/*

Get and print the maximum information on CPU from user mode.

Linux has a description of the PowerPC processors in arch/power/kernel/cputable.c

To get information about the CPU, on Linux:
- Access PVR that is a privileged register but the generated exception
  is handled by the kernel to read and return the value.
- Look into this filesystem entry: /sys/devices/system/cpu/ mainly to get
  the caches properties.
- Read /proc/self/auxv that contains some hardware capabilities.

gcc -Wall -o cpuinfo_linux cpuinfo_linux.c

*/

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/auxvec.h>
#include <asm/cputable.h>

/*
 * Based from an exemple to detect AltiVec originally provided at
 * http://freevec.org/function/altivec_runtime_detection_linux
 */
static inline unsigned int GetHardwareCapabilities(void)
{
   unsigned int result = 0;
   unsigned long buf[64];
   ssize_t count;
   int fd, i;

   fd = open("/proc/self/auxv", O_RDONLY);
   if (fd < 0) {
      return 0;
   }
   // Loop on reading and stop when found tag on hardware capabilities
   do {
      count = read(fd, buf, sizeof(buf));
      if (count < 0){                                                                                                                                                     
         break;                          
      }                                                                                                                                
      for (i=0; i < (count / sizeof(unsigned long)); i += 2) {                                                                                                            
         if (buf[i] == AT_HWCAP) {
            // Return the bitfield value                                                                                                                                      
            result = buf[i+1];
            goto out_close;
         } else if (buf[i] == AT_NULL) {
            goto out_close;
         }
      }
   } while (count == sizeof(buf));
out_close:
   close(fd);
   return result;
}

/*
 * Cache features are read from the filesystem at /sys/devices/system/cpu/cpu0/cache/indexN
 * Available properties are: coherency_line_size, level, number_of_sets, shared_cpu_map,
 * size, type, ways_of_associativity.
 * Return in case of success, 0 otherwise
 */
static int print_cache_features(int index)
{
   char cache_index_path[64] = "/sys/devices/system/cpu/cpu0/cache/index";
   char pathname[64];
   char charbuf[16];
   int fd;
   int count;
   int level;

   // Level
   sprintf(pathname, "%s%d/level", cache_index_path, index);
   fd = open(pathname, O_RDONLY);
   if (fd < 0) {
      return 0;
   }
   count = read(fd, charbuf, 16);
   charbuf[count - 1] = 0;
   level = charbuf[0] - 0x30;

   // Type
   sprintf(pathname, "%s%d/type", cache_index_path, index);
   fd = open(pathname, O_RDONLY);
   if (fd < 0) {
      return 0;
   }
   count = read(fd, charbuf, 16);
   charbuf[count - 1] = 0;

   printf("\nL%d cache - %s\n", level, charbuf);

   // Size
   sprintf(pathname, "%s%d/size", cache_index_path, index);
   fd = open(pathname, O_RDONLY);
   if (fd < 0) {
      return 0;
   }
   count = read(fd, charbuf, 16);
   charbuf[count - 1] = 0;
   printf("Size         : %s\n", charbuf);

   // Line size
   sprintf(pathname, "%s%d/coherency_line_size", cache_index_path, index);
   fd = open(pathname, O_RDONLY);
   if (fd < 0) {
      return 0;
   }
   count = read(fd, charbuf, 16);
   charbuf[count - 1] = 0;
   printf("Line size    : %s bytes\n", charbuf);

   // Number of sets
   sprintf(pathname, "%s%d/number_of_sets", cache_index_path, index);
   fd = open(pathname, O_RDONLY);
   if (fd < 0) {
      return 0;
   }
   count = read(fd, charbuf, 16);
   charbuf[count - 1] = 0;
   printf("Sets         : %s\n", charbuf);

   // Associativity
   sprintf(pathname, "%s%d/ways_of_associativity", cache_index_path, index);
   fd = open(pathname, O_RDONLY);
   if (fd < 0) {
      return 0;
   }
   count = read(fd, charbuf, 16);
   charbuf[count - 1] = 0;
   printf("Associativity: %s\n", charbuf);

   return 1;
}

static inline unsigned int get_pvr(void)
{
   unsigned int pvr;
   asm volatile ("mfspr %0, 287" : "=r" (pvr));
   return pvr;
}

int main()
{
   unsigned int pvr;
   unsigned int hwcaps;

   hwcaps = GetHardwareCapabilities();

   if (hwcaps & PPC_FEATURE_64) {
      printf("CPU     : 64-bit\n");
   } else {
      printf("CPU     : 32-bit\n");
   }

   pvr = get_pvr();
   printf("PVR     : 0x%08x\n", pvr);

   if (hwcaps & PPC_FEATURE_HAS_MMU) {
      printf("MMU     : Yes\n");
   } else {
      printf("MMU     : No\n");
   }

   if (hwcaps & PPC_FEATURE_HAS_FPU) {
      printf("FPU     : Yes\n");
   } else {
      printf("FPU     : No\n");
   }

   if (hwcaps & PPC_FEATURE_HAS_ALTIVEC) {
      printf("AltiVec : Yes\n");
   } else {
      printf("AltiVec : No\n");
   }

   if (hwcaps & PPC_FEATURE_HAS_4xxMAC) {
      printf("4xx MAC : Yes\n");
   } else {
      printf("4xx MAC : No\n");
   }

   if (hwcaps & PPC_FEATURE_HAS_SPE) {
      printf("SPE     : Yes\n");
   } else {
      printf("SPE     : No\n");
   }

   if (hwcaps & PPC_FEATURE_ARCH_2_06) {
      printf("PowerISA: 2.06\n");
   } else if (hwcaps & PPC_FEATURE_ARCH_2_05) {
      printf("PowerISA: 2.05\n");
   } else {
      printf("PowerISA: Unknown\n");
   }

   if (hwcaps & PPC_FEATURE_BOOKE) {
      printf("Book E  : Yes\n");
   } else {
      printf("Book E  : No\n");
   }

   if (hwcaps & PPC_FEATURE_PPC_LE) {
      printf("LE mode : Yes\n");
   } else {
      printf("LE mode : No\n");
   }

   // Typically, there are 3 cache entities: inst. L1, data L1, L2
   print_cache_features(0);
   print_cache_features(1);
   print_cache_features(2);

   return 0;
}

